package com.shop.filter;

import com.alibaba.fastjson.JSON;
import com.shop.entity.CommonUser;
import com.shop.entity.MerchantMerchant;
import com.shop.entity.SupplierSupplier;
import com.shop.entity.User;
import com.shop.model.dto.Authorization;
import com.shop.model.vo.ElasticSearchLog;
import com.shop.service.RedisService;
import com.shop.utils.JasyptUtils;
import io.netty.buffer.ByteBufAllocator;
import lombok.AllArgsConstructor;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.io.IOException;
import java.net.URI;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @BelongsProject: shop
 * @BelongsPackage: com.shop.filter
 * @Author: GaoDo
 * reateTime: 2020-11-22 20:44
 * @Description: 鉴权用的过滤器
 */
@Order(-100)
@Component
@AllArgsConstructor
public class AuthFilter implements GlobalFilter {

    private final RestHighLevelClient client;


    private final RedisService redisService;


    private final Executor executor;


    //日志记录，方便调试
    private static final Logger log = LoggerFactory.getLogger(AuthFilter.class);

    //白名单
    private static List<String> whiteUrlList;

    static {
        String[] s = {"/qiniu/image","/login", "/miniAppLogin", "/getUserInfo", "/logout"};
        whiteUrlList = Arrays.asList(s);
    }

    //日志记录白名单
    private static List<String> whiteLogList;

    static {
        String[] str = {"/getLog", "/getRequest"};
        whiteLogList = Arrays.asList(str);
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //获取请求路径
        String url = exchange.getRequest().getURI().getPath();
        AuthFilter.log.info("请求  路径：{}", url);
        //请求白名单路径 跳过验证
        if (whiteUrlList.contains(url)) {
            System.out.println("经过白名单");
            return chain.filter(exchange);
        }
        //取出token
        String token = exchange.getRequest().getHeaders().getFirst("token");
        //token非空校验
        if (token == null || token.isEmpty()) {
            return errorRsp("token不能为空", exchange);
        }
        //token合法性和过期校验
        CommonUser user = null;
        Integer userId = null;
        String roleSign = null;
        String ip = null;
        String requestDetail = "";
        MerchantMerchant merchant = null;
        SupplierSupplier supplier = null;
        String name = null;
        try {
            token = token.replaceAll(" ", "+");
            String key = JasyptUtils.decryptPwd(token);
            //判断是否是小程序登陆
            if (key.contains("user")) {
                User miniUser = (User) redisService.get(key);
                userId = miniUser.getId();
                roleSign = "user";
                name = miniUser.getName();
            } else {
                Authorization redisUser = (Authorization) redisService.get(key);
                user = redisUser.getUser();
                userId = user.getId();
                roleSign = user.getRole();
                name = user.getAccount();
                if (roleSign.equals("merchant")) {
                    merchant = (MerchantMerchant) redisUser.getInfo();
                } else if (roleSign.equals("supplier")) {
                    supplier = (SupplierSupplier) redisUser.getInfo();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            return errorRsp("登陆信息无效：不合法或过期", exchange);
        }
        //token载荷信息校验
        //以用户id为键 键给用户的请求次数 写入redis 同时个i这个键值对设置过期1S

        //接口级别  以访问路径为key

        //集群级别限流 以集群名称为key

        //ip级别限流 以ip为键


        if (userId == null || userId == 0) {
            return errorRsp("登陆信息无效：用户信息不存在或被冻结", exchange);
        }

        ServerHttpRequest serverHttpRequest = exchange.getRequest();
        String method = serverHttpRequest.getMethodValue();
        //为post请求时
        if ("POST".equals(method)) {
            //从请求里获取Post请求体
            String bodyStr = resolveBodyFromRequest(serverHttpRequest);
            //下面的将请求体再次封装写回到request里，传到下一级，否则，由于请求体已被消费，后续的服务将取不到值
            URI uri = serverHttpRequest.getURI();
            ServerHttpRequest request = serverHttpRequest.mutate().uri(uri).build();
            DataBuffer bodyDataBuffer = stringBuffer(bodyStr);
            requestDetail=bodyStr;
            Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);

            request = new ServerHttpRequestDecorator(request) {
                @Override
                public Flux<DataBuffer> getBody() {
                    return bodyFlux;
                }
            };
            ServerWebExchange newExchange = exchange.mutate().request(request).build();
            ip = Objects.requireNonNull(request.getRemoteAddress()).getAddress().getHostAddress();
            newExchange.getRequest().mutate().header("userId", userId + "")
                    .header("roleSign", roleSign)
                    .header("merchantId", merchant == null ? "" : merchant.getId() + "")
                    .header("supplierId", supplier == null ? "" : supplier.getId() + "")
                    .build();
            //准备填写日志
            ElasticSearchLog searchLog = new ElasticSearchLog();
            searchLog.setRequestData(requestDetail);
            searchLog.setName(name);
            searchLog.setUri(url);
            searchLog.setIp(ip);
            //不在白名单则调用日志
            executor.execute(()->{
                if (!whiteLogList.contains(url)) {
                    this.recordLog(searchLog);
                }
            });

            return chain.filter(newExchange);
        }
        //为get请求
        if ("GET".equals(method)) {
            ServerHttpRequest newRequest = exchange.getRequest().mutate().header("userId", userId + "")
                    .header("roleSign", roleSign)
                    .header("merchantId", merchant == null ? "" : merchant.getId() + "")
                    .header("supplierId", supplier == null ? "" : supplier.getId() + "")
                    .build();
            ServerWebExchange serverWebExchange = exchange.mutate().request(newRequest).build();
            //获取请求详细参数
            ip = Objects.requireNonNull(newRequest.getRemoteAddress()).getAddress().getHostAddress();
            MultiValueMap<String, String> map = newRequest.getQueryParams();
            for (String key : map.keySet()) {
                requestDetail = requestDetail + key + ":" + map.get(key) + ";";
            }
            //准备填写日志
            ElasticSearchLog searchLog = new ElasticSearchLog();
            searchLog.setRequestData(requestDetail);
            searchLog.setName(name);
            searchLog.setUri(url);
            searchLog.setIp(ip);
            //不在白名单则调用日志
            executor.execute(()->{
                if (!whiteLogList.contains(url)) {
                    this.recordLog(searchLog);
                }
            });
            return chain.filter(exchange);
        }
        //既不是get也不是post直接返回
        return chain.filter(exchange);
    }


    /**
     * 生成错误响应信息
     */
    public Mono<Void> errorRsp(String msg, ServerWebExchange exchange) {
        ServerHttpResponse rsp = exchange.getResponse();
        rsp.setStatusCode(HttpStatus.OK);
        rsp.getHeaders().add("Content-Type", "application/json;charset=UTF-8");

        byte[] responseByte = ("{\"code\":50008,\"msg\":\"" + msg +
                "\",\"content\":null}")
                .getBytes(StandardCharsets.UTF_8);

        DataBuffer buffer = rsp.bufferFactory().wrap(responseByte);

        return rsp.writeWith(Flux.just(buffer));
    }

    /**
     * 记录日志
     */
    void recordLog(ElasticSearchLog searchLog) {

        searchLog.setTime(new Date());
        //创建请求
        IndexRequest request = new IndexRequest("logstash");
        //规则 put 不设置则自动生成id
//        request.id("1");
        //两种超时方式均可  request.timeout(TimeValue.timeValueSeconds(1));
        request.timeout("1s");
        //将数据放入请求

        request.source(JSON.toJSONString(searchLog), XContentType.JSON);
        System.out.println(searchLog.toString());
        //客户端发送请求
        IndexResponse indexResponse = null;
        try {
            indexResponse = client.index(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println(indexResponse.toString());
        System.out.println(indexResponse.status());
    }

    /**
     * 从Flux<DataBuffer>中获取字符串的方法
     *
     * @return 请求体
     */
    private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) {
        //获取请求体
        Flux<DataBuffer> body = serverHttpRequest.getBody();
        AtomicReference<String> bodyRef = new AtomicReference<>();
        body.subscribe(buffer -> {
            CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
            DataBufferUtils.release(buffer);
            bodyRef.set(charBuffer.toString());
        });
        //获取request body
        return bodyRef.get();
    }

    private DataBuffer stringBuffer(String value) {
        if(StringUtils.isEmpty(value)){
            value="";
        }
        byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
        NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
        DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
        buffer.write(bytes);
        return buffer;
    }
}
